package proto

import (
	"encoding/binary"
	"fmt"
	"io"
	"lark/conf"
	"net"
	"sync/atomic"
	"time"
)

type LarkProtoV2 struct {
	readTimeout  time.Duration
	writeTimeout time.Duration
	conn         net.Conn
}

func NewlarkProtoV2(conn net.Conn, readTimeout, writeTimeout time.Duration) *LarkProtoV2 {
	return &LarkProtoV2{
		readTimeout:  readTimeout,
		writeTimeout: writeTimeout,
		conn:         conn,
	}
}

func (p *LarkProtoV2) Read() (tag uint16, payload []byte, err error) {
	return p.ReadTimeout(p.readTimeout)
}

func (p *LarkProtoV2) ReadTimeout(timeout time.Duration) (tag uint16, payload []byte, err error) {
	//return p.ReadTimeout1(timeout)

	var deadline time.Time
	if timeout != 0 {
		deadline = time.Now().Add(timeout)
	}

	p.conn.SetReadDeadline(deadline)

	var header = make([]byte, lengthOfHeader)
	_, err = io.ReadFull(p.conn, header)
	if err != nil {
		return
	}

	payloadLen := binary.BigEndian.Uint32(header[lengthOfTag:])
	if payloadLen > atomic.LoadUint32(&conf.MaxSizeOfNetPackage) {
		var b = make([]byte, 128)
		n, _ := p.conn.Read(b)
		err = fmt.Errorf("payload len too long, %q \n", b[:n])
		return
	}

	payload = make([]byte, payloadLen)

	_, err = io.ReadFull(p.conn, payload)
	if err != nil {
		return
	}

	tag = binary.BigEndian.Uint16(header[:lengthOfTag])
	return
}

/*
func (p *LarkProtoV2) ReadTimeout1(timeout time.Duration) (uint16, []byte, error) {
	var isDeadlineSet bool

	if p.bufTail < lengthOfHeader {
		if timeout != 0 {
			p.conn.SetReadDeadline(time.Now().Add(timeout))
			isDeadlineSet = true
		}

		least := lengthOfHeader - p.bufTail
		n, err := io.ReadAtLeast(p.conn, p.buf[p.bufTail:], least)
		if err != nil {
			return 0, nil, err
		}
		p.bufTail += n
	}

	payloadLen := int(binary.BigEndian.Uint32(p.buf[lengthOfTag:lengthOfHeader]))
	if p.bufTail < payloadLen+lengthOfHeader {
		if !isDeadlineSet && timeout != 0 {
			p.conn.SetReadDeadline(time.Now().Add(timeout))
			isDeadlineSet = true
		}

		least := payloadLen + lengthOfHeader - p.bufTail
		n, err := io.ReadAtLeast(p.conn, p.buf[p.bufTail:], least)
		if err != nil {
			return 0, nil, err
		}
		p.bufTail += n
	}

	tag := binary.BigEndian.Uint16(p.buf[:lengthOfTag])
	payload := make([]byte, payloadLen)
	copy(payload, p.buf[lengthOfHeader:lengthOfHeader+payloadLen])
	p.bufTail = copy(p.buf, p.buf[lengthOfHeader+payloadLen:p.bufTail])

	return tag, payload, nil
}
*/

func (p *LarkProtoV2) Write(tag uint16, segments ...[]byte) error {
	return p.WriteWithTimeout(p.writeTimeout, tag, segments...)
}

func (p *LarkProtoV2) WriteWithTimeout(timeout time.Duration, tag uint16, segments ...[]byte) error {
	var deadline time.Time
	if timeout != 0 {
		deadline = time.Now().Add(timeout)
	}
	p.conn.SetWriteDeadline(deadline)

	_, err := p.conn.Write(PackLarkProtoV2(tag, segments...))
	return err
}

func PackLarkProtoV2(tag uint16, segments ...[]byte) []byte {
	var totalLen int
	for _, segment := range segments {
		totalLen += lengthOflength + len(segment)
	}

	//TODO 可以做一个 pool
	var data = make([]byte, totalLen+lengthOfHeader)

	binary.BigEndian.PutUint16(data[:lengthOfTag], tag)
	binary.BigEndian.PutUint32(data[lengthOfTag:lengthOfHeader], uint32(totalLen))

	var offset = lengthOfHeader
	for _, segment := range segments {
		binary.BigEndian.PutUint32(data[offset:offset+lengthOflength], uint32(len(segment)))
		copy(data[offset+lengthOflength:offset+lengthOflength+len(segment)], segment)
		offset += lengthOflength + len(segment)
	}
	return data[:offset]
}

func (p *LarkProtoV2) RemoteAddr() net.Addr {
	return p.conn.RemoteAddr()
}

func (p *LarkProtoV2) Close() error {
	return p.conn.Close()
}

func SplitPayload(payload []byte) [][]byte {
	var value [][]byte
	var offset int
	for offset < len(payload) {
		l := int(binary.BigEndian.Uint32(payload[offset : offset+4]))
		value = append(value, payload[offset+4:offset+4+l])
		offset += 4 + l
	}
	return value
}
